#pragma once
#include <iostream>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <functional>
#include "noCopy.hpp"
#include "log.hpp"
#include "comm.h"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"


static const int defaultport = 8888;
static const int defaultfd = -1;
static const int defaultsize = 1024;

using cb_t = std::function<void()>;

class UdpServer : public noCopy
{
public:
    UdpServer(const uint16_t port = defaultport):
            _port(port),
            _sockfd(defaultfd)
            {}

    // 初始化服务器的过程：
    // 1. 创建套接字文件：在内核中创建一个通信端点
    // 2. 填充sockaddr地址结构体：指定服务器运行在哪个ip和port上
    // 3. 将套接字文件与sockaddr结构体进行绑定：将套接字描述符与特定ip和port绑定
    void Init()
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            lg.LogMessage(Fatal, "Sock Error, %d: %s", errno, strerror(errno));
            exit(sockError);
        }
        lg.LogMessage(Info, "sock success, sockfd: %d", _sockfd);

        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY; // 绑定任意ip地址

        int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(n != 0)
        {
            lg.LogMessage(Fatal, "bind failed, %d: %s", errno, strerror(errno));
            exit(bindError);
        }
        ThreadPool<cb_t>::GetInstance()->Start(); // 我们在初始化服务器时，启动线程池
    }
    
    void AddOnlineUser(InetAddr& peer)
    {
        LockGuard lock(&_UserMutex); // 对用户列表的访问要上锁，防止线程错误
        // 如果已经在列表，则不加入
        for(auto& user : _onlineUsers)
        {
            if(peer == user) return ;
        }
        _onlineUsers.push_back(peer);
        lg.LogMessage(Debug, "%s:%d is add to OnlineList!", peer.Ip().c_str(), peer.Port());
    }
    
    void Routine(int sockfd, const std::string& message)
    {
        LockGuard lock(&_UserMutex);
        for(auto& user : _onlineUsers)
        {
            sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&user.GetAddr(), sizeof(user.GetAddr()));
            lg.LogMessage(Debug, "send message to user %s:%d, message: %s", user.Ip().c_str(), user.Port(), message.c_str());
        }
    }

    // chat服务器的思路：
    // 接收客户端发过来的消息后，将客户端的ip和port作为客户名称，加入在线用户列表
    // 然后向所有客户端发送用户发的消息
    void Start()
    {
        char buffer[defaultsize];
        for(;;)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
            if(n > 0)
            {
                InetAddr addr(peer); 
                AddOnlineUser(addr);
                buffer[n] = 0;
                // 例子：[ip:port]# what can i say
                std::string message = "[";
                message += addr.Ip();
                message += ":";
                message += to_string(addr.Port());
                message += "]# ";
                message += buffer;

                // 向所有客户端发送用户发的消息
                // 向线程池传入用于转发消息的可调用对象
                cb_t task = bind(&UdpServer::Routine, this, _sockfd, message);
                ThreadPool<cb_t>::GetInstance()->Push(task);
            }
            
        }
    }
    
    ~UdpServer()
    {
        pthread_mutex_destroy(&_UserMutex);
    }

private:

    uint16_t _port;
    int _sockfd;

     // 用ip和port登记在线用户
    std::vector<InetAddr> _onlineUsers;

    // 由于引入了线程池，我们必须保证任何时刻只有一个线程在使用在线用户列表
    pthread_mutex_t _UserMutex = PTHREAD_MUTEX_INITIALIZER;
};